﻿using System;
using FizzWare.NBuilder;
using System.Data.Entity;

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Data.Objects;
using System.Data.Entity.Validation;
using DomusDotNet.Sbm.ObjectModel.DbModel;
using System.Data.Entity.Infrastructure;

namespace DomusDotNet.Sbm.ObjectModel.Test
{
    /// <summary>
    ///This is a test class for DbTest and is intended
    ///to contain all DbTest Unit Tests
    ///</summary>
    [TestClass()]
    public class DbTest
    {
        private TestContext testContextInstance;

        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        public TestContext TestContext
        {
            get
            {
                return testContextInstance;
            }
            set
            {
                testContextInstance = value;
            }
        }

        #region Additional test attributes
        // 
        //You can use the following additional attributes as you write your tests:
        //
        //Use ClassInitialize to run code before running the first test in the class
        //[ClassInitialize()]
        //public static void MyClassInitialize(TestContext testContext)
        //{
        //}
        //
        //Use ClassCleanup to run code after all tests in a class have run
        //[ClassCleanup()]
        //public static void MyClassCleanup()
        //{
        //}
        //
        //Use TestInitialize to run code before running each test
        //[TestInitialize()]
        //public void MyTestInitialize()
        //{
        //}
        //
        //Use TestCleanup to run code after each test has run
        //[TestCleanup()]
        //public void MyTestCleanup()
        //{
        //}
        //
        #endregion

        /// <summary>
        /// Create database.
        /// </summary>
        [ClassInitialize()]
        public static void CreateDatabase(TestContext testContext)
        {

            Database.SetInitializer<Db>(new CustomDatabaseInitializerStrategy<Db>());
            ////Database.SetInitializer<Db>(new DropCreateDatabaseAlways<Db>());

            using (Db db = new Db())
            {
                try
                {
                    //db.Configuration.ProxyCreationEnabled = false;
                    //db.Configuration.AutoDetectChangesEnabled = false;

                    ////Add five members.
                    Member[] members = Builder<Member>.CreateListOfSize(3).WhereAll()
                         .Have(m => m.Address = Builder<Address>.CreateNew().With(a => a.ZipCode = "12345").Build())
                         .Build().ToArray();

                    db.Members.Add(members[0]);
                    db.Members.Add(members[1]);
                    db.Members.Add(members[2]);

                    Book book1 = CreateNewBook(members[0], "Programming C#", 3);
                    Book book2 = CreateNewBook(members[1], "Programming ASP.NET", 1);
                    Book book3 = CreateNewBook(members[2], "SQL Server 2008R2 Step By Step", 5);

                    db.Books.Add(book1);
                    db.Books.Add(book2);
                    db.Books.Add(book3);

                    int rowsAffected = db.SaveChanges();
                }
                catch (DbEntityValidationException ex)
                {
                    foreach (DbEntityValidationResult dbValidationResult in ex.EntityValidationErrors)
                    {
                        if (!dbValidationResult.IsValid)
                        {
                            foreach (DbValidationError dbValidationError in dbValidationResult.ValidationErrors)
                            {
                                Assert.Inconclusive("Error in {0}: {1}", dbValidationError.PropertyName, dbValidationError.ErrorMessage);
                            }
                        }
                    }
                }
            }
        }

        [TestMethod()]
        public void CheckDatabase()
        {
            using (Db db = new Db())
            {
                Assert.AreEqual<int>(3, db.Members.Count());
                Assert.AreEqual<int>(3, db.Books.Count());
                Assert.AreEqual<int>(3, db.Editors.Count());
                Assert.AreEqual<int>(9, db.Authors.Count());
            }
        }

        [TestMethod()]
        public void AuthenticationCheck()
        {
            using (Db db = new Db())
            {
                Member member = (from m in db.Members where m.Password == "Password1" && m.Username == "Username1" select m).FirstOrDefault();
                Assert.IsNotNull(member);
                Assert.AreEqual<String>("Name1", member.Name);
                Assert.AreEqual<String>("Username1", member.Username);
                Assert.AreEqual<String>("12345", member.Address.ZipCode);
            }
        }

        /// <summary>
        /// This method get all books.
        /// </summary>
        [TestMethod()]
        public void GetAllBooks()
        {
            using (Db db = new Db())
            {
                IList<Book> booksList = (from b in db.Books.Include(b => b.Authors).Include(b => b.Member) select b).ToList();
                Assert.IsNotNull(booksList);
                Assert.AreEqual(3, booksList.Count());
            }
        }

        /// <summary>
        /// This method find books by title.
        /// </summary>
        [TestMethod()]
        public void GetBooksByTitle()
        {
            using (Db db = new Db())
            {
                string title = "programming";

                IList<Book> books = (from b in db.Books.Include(b => b.Authors).Include(b => b.Member).Include(b => b.Editor)
                                     where b.Title.Contains(title)
                                     orderby b.ISBN
                                     select b).ToList();

                Assert.AreEqual(2, books.Count);

                ////Set book as not available.
                Book book = books.First();

                book.Available = true;

                db.SaveChanges();

                ////Get only availables books.
                IList<Book> availablesBooks = (from b in db.Books.Include(b => b.Authors).Include(b => b.Member).Include(b => b.Editor)
                                               where b.Title.Contains(title) && b.Available == true
                                               orderby b.ISBN
                                               select b).ToList();

                Assert.AreEqual(2, books.Count);
            }
        }


        /// <summary>
        /// This method find books by title.
        /// </summary>
        [TestMethod()]
        public void DbBooksLocalAndFindTest()
        {
            using (Db db = new Db())
            {
                Assert.AreEqual<int>(0, db.Books.Local.Count);

                IList<Book> books = (from b in db.Books.Include(b => b.Authors).Include(b => b.Member).Include(b => b.Editor)
                                     orderby b.ISBN
                                     select b).ToList();

                Assert.AreEqual<int>(db.Books.Local.Count, books.Count);

                Member member = (from m in db.Members select m).FirstOrDefault();

                ////Add a new book in db.Books collection, but doesn't save it.
                Book newBook = CreateNewBook(member, "My Book Title", 3);
                db.Books.Add(newBook);

                Assert.AreEqual<int>(books.Count + 1, db.Books.Local.Count);

                ////Find, in memory.                
                Book findResult = db.Books.Find(newBook.ID);
                Assert.IsNotNull(findResult);

                Assert.AreEqual<String>(newBook.Title, findResult.Title);

                db.Books.Remove(findResult);

                Assert.AreEqual<int>(db.Books.Local.Count, books.Count);
            }
        }

        [TestMethod()]
        public void GetBooksByStoredProcedureTest()
        {
            using (Db db = new Db())
            {
                ////No Entities Tracking.
                IEnumerable<Book> result1 = db.Database.SqlQuery<Book>("EXECUTE GetBooks @Title", new System.Data.SqlClient.SqlParameter("Title", "programming"));
                Assert.AreEqual<int>(2, result1.Count());

                Assert.AreEqual<int>(0, db.Books.Local.Count);

                ////Entities Tracking.
                IEnumerable<Book> result2 = db.Books.SqlQuery("EXECUTE GetBooks @Title", new System.Data.SqlClient.SqlParameter("Title", "programming"));
                Assert.AreEqual<int>(2, result2.Count());

                Assert.AreEqual<int>(2, db.Books.Local.Count);
            }
        }

        [TestMethod()]
        public void OptimisticConcurrencyStoreWinsTest()
        {
            ////Store Wins.
            Db db1 = new Db();
            Db db2 = new Db();

            Book bookFromDb1 = (from b in db1.Books.Include(b => b.Authors)
                             .Include(b => b.Editor)
                             .Include(b => b.Member)
                                select b).FirstOrDefault();

            Book bookFromDb2 = (from b in db2.Books.Include(b => b.Authors)
                             .Include(b => b.Editor)
                             .Include(b => b.Member)
                                select b).FirstOrDefault();

            Assert.IsNotNull(bookFromDb1);
            Assert.IsNotNull(bookFromDb2);

            bookFromDb1.Title = "Pascal";
            db1.SaveChanges();

               bookFromDb2.Title = "Programming Turbo Pascal";

            try
            {
                db2.SaveChanges();
            }
            catch (DbUpdateConcurrencyException ex)
            {
                ////Reload entity values.
                ex.Entries.Single().Reload();

                db2.SaveChanges();

                Assert.AreEqual(bookFromDb1.Title, "Pascal");
            }

            db1.Dispose();
            db2.Dispose();
        }

        [TestMethod()]
        public void OptimisticConcurrencyClientWinsTest()
        {
            ////Store Wins.
            Db db1 = new Db();
            Db db2 = new Db();

            Book bookFromDb1 = (from b in db1.Books.Include(b => b.Authors)
                             .Include(b => b.Editor)
                             .Include(b => b.Member)
                                select b).FirstOrDefault();

            Book bookFromDb2 = (from b in db2.Books.Include(b => b.Authors)
                             .Include(b => b.Editor)
                             .Include(b => b.Member)
                                select b).FirstOrDefault();

            Assert.IsNotNull(bookFromDb1);
            Assert.IsNotNull(bookFromDb2);

            bookFromDb1.Title = "Pascal Client";
            bookFromDb2.Title = "Programming Turbo Pascal GUI";
            db1.SaveChanges();

            try
            {
                db2.SaveChanges();
            }
            catch (DbUpdateConcurrencyException ex)
            {
                //////Reload entity values.   
                //ex.Entries.Single().OriginalValues.SetValues(ex.Entries.Single().GetDatabaseValues());

                //db2.SaveChanges();

                Assert.AreEqual(bookFromDb1.Title, "Pascal Client");
            }

            db1.Dispose();
            db2.Dispose();
        }

        private static Book CreateNewBook(Member member, string title, int authorsCount)
        {
            ////Create three authors collection.
            IList<Author> authorsForBook1 = Builder<Author>.CreateListOfSize(authorsCount).Build();
            Editor editorBook = Builder<Editor>.CreateNew().With(e => e.Address = Builder<Address>.CreateNew().With(a => a.ZipCode = "12345").Build()).Build();

            Book book = Builder<Book>.CreateNew()
                .With(b => b.Authors = authorsForBook1)
                .With(b => b.Title = title)
                .With(b => b.Available = true)
                .With(b => b.Editor = editorBook)
                .With(b => b.Member = member)
                .With(b => b.ID = -1)
                .Build();

            return book;
        }
    }
}
